﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.Web.Mvc;

namespace MvcImproved.Generic
{
	public class GenericPageParser : PageParserFilter
	{
		private enum DirectiveType
		{
			Unknown,
			Page,
			Control,
			Master
		}

		private string _ViewBaseType;
		private DirectiveType _DirectiveType = DirectiveType.Unknown;
		private bool _ViewTypeControlAdded;
		internal string _GenericParameters;
		internal GenericControlTypeAttribute _GenericData;

		private bool TryGetBaseClass(Type baseType, string attribute, IDictionary attributes)
		{
			string inherits = (string)attributes[attribute];
			if (!String.IsNullOrEmpty(inherits))
			{
				//Check to see if generic attributes were specified in type itself, mimicking MVC default behavior
				//Accept VB/C# generics specification
				if (inherits.IndexOfAny(new char[] { '(', '<' }) >= 0)
				{
					// Remove the templatizing from the base attribute
					attributes[attribute] = baseType.FullName;

					// Track type string to later pass to ControlBuilder;
					_ViewBaseType = inherits;

					return true;
				}
				Type genericType = System.Web.Compilation.BuildManager.GetType(inherits, true);
				GenericControlTypeAttribute data = genericType.GetCustomAttributes(typeof(GenericControlTypeAttribute), true).Cast<GenericControlTypeAttribute>().FirstOrDefault();
				if (data != null)
				{
					_GenericData = data;
					_GenericParameters = (string)attributes[data.GenericParameters];
					attributes.Remove(data.GenericParameters);
				}
			}

			return false;
		}

		public override void PreprocessDirective(string directiveName, IDictionary attributes)
		{
			try
			{
				_DirectiveType = (DirectiveType)Enum.Parse(typeof(DirectiveType), directiveName, true);
			}
			catch (Exception)
			{
				_DirectiveType = DirectiveType.Unknown;
			}

			if (_DirectiveType != DirectiveType.Unknown)
			{
				Type baseType = null;
				switch (_DirectiveType)
				{
					case DirectiveType.Page:
						baseType = typeof(System.Web.Mvc.ViewPage);
						break;
					case DirectiveType.Control:
						if (attributes.Contains("ModelType"))
							baseType = typeof(MvcImproved.Generic.ViewUserControl);
						else
							baseType = typeof(System.Web.Mvc.ViewUserControl);
						break;
					case DirectiveType.Master:
						baseType = typeof(System.Web.Mvc.ViewMasterPage);
						break;
				}

				if (!TryGetBaseClass(baseType, "inherits", attributes))
					TryGetBaseClass(baseType, "codefilebaseclass", attributes);
			}

			base.PreprocessDirective(directiveName, attributes);
		}

		public override void ParseComplete(ControlBuilder rootBuilder)
		{
			base.ParseComplete(rootBuilder);

			// If it's our generic ControlBuilder, initialize PageBaseType
			IGenericControlBuilder pageBuilder = rootBuilder as IGenericControlBuilder;
			if (pageBuilder != null)
				pageBuilder.PageBaseTypeName = _ViewBaseType;
			else
			{
				//MVC "Hack" to keep MVC behavior
				Type userControlBuilderType = System.Web.Compilation.BuildManager.GetType("System.Web.Mvc.ViewUserControlControlBuilder", true);
				Type pageBuilderType = System.Web.Compilation.BuildManager.GetType("System.Web.Mvc.ViewPageControlBuilder", true);
				if (rootBuilder.GetType() == userControlBuilderType)
					userControlBuilderType.GetProperty("UserControlBaseType", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).SetValue(rootBuilder, _ViewBaseType, null);
				else if (rootBuilder.GetType() == pageBuilderType)
					pageBuilderType.GetProperty("PageBaseType", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).SetValue(rootBuilder, _ViewBaseType, null);
			}
		}

		//Replicate default MVC behavior (would not be necessary if ViewPageControlBuilder wasn't an internal class
		public override bool ProcessCodeConstruct(CodeConstructType codeType, string code)
		{
			if (codeType == CodeConstructType.ExpressionSnippet &&
				!_ViewTypeControlAdded &&
				_ViewBaseType != null &&
				_DirectiveType == DirectiveType.Master)
			{

				// If we're dealing with a master page that needs to have its base type set, do it here.
				// It's done by adding the ViewType control, which has a builder that sets the base type.

				// The code currently assumes that the file in question contains a code snippet, since
				// that's the item we key off of in order to know when to add the ViewType control.

				Dictionary<string, string> attribs = new Dictionary<string, string>();
				attribs["typename"] = _ViewBaseType;
				AddControl(typeof(ViewType), attribs);
				_ViewTypeControlAdded = true;
			}

			return base.ProcessCodeConstruct(codeType, code);
		}

		//Default = allow everything
		public override bool AllowCode
		{
			get
			{
				return true;
			}
		}

		public override bool AllowBaseType(Type baseType)
		{
			return true;
		}

		public override bool AllowControl(Type controlType, ControlBuilder builder)
		{
			return true;
		}

		public override bool AllowVirtualReference(string referenceVirtualPath, VirtualReferenceType referenceType)
		{
			return true;
		}

		public override bool AllowServerSideInclude(string includeVirtualPath)
		{
			return true;
		}

		public override int NumberOfControlsAllowed
		{
			get
			{
				return -1;
			}
		}

		public override int NumberOfDirectDependenciesAllowed
		{
			get
			{
				return -1;
			}
		}

		public override int TotalNumberOfDependenciesAllowed
		{
			get
			{
				return -1;
			}
		}
	}
}
